/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package util;

import controllers.DatabaseController;
import controllers.SearchController;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.PriorityQueue;
import javax.swing.JFrame;
import listcontrol.RemoteFileList;
import views.DownloadDetails;

/**
 *
 * @author Victor
 */
public class IncompleteFile extends File implements Runnable {

    public static int MAX_CONNECTIONS;
    public static int MAX_CONNECTIONS_PER_PEER;
    public static int CHUNK_SIZE = 512 * 1024;
    
    private RemoteFile file;
    //private final PriorityQueue<Long> parts;
    private final ArrayList<Long> parts;
    private long nParts;        
    
    private final DatabaseController dbcontroller;            
    private final DownloadProperties properties;
    private DownloadDetails detailsWindow;    
        

    //deve possuir partes e indexar cada uma com o índice dos peers em filePeers
    // para saber o ip, usa-se o dbcontroller
    // deve permitir a inclusão de cada parte e verificação de arquivo completo
    public IncompleteFile(RemoteFile file, DatabaseController dbcontroller) throws IOException {          
        this.dbcontroller = dbcontroller;
        this.parts = new ArrayList<>();       
        this.setSize(file.getSize());    
        this.setName(file.getName());
        
        this.file = file;

        this.properties = new DownloadProperties();
        this.properties.setDownloadedParts(0);
        this.properties.setParts(parts);
        this.properties.setnConn(0);
        this.properties.setAmoutDownloaded(0);

        nParts = file.getSize() / CHUNK_SIZE;
        if (file.getSize() % CHUNK_SIZE != 0) {
            nParts++;
        }
        for (long i = 0; i < nParts; i++) {
            this.parts.add(new Long(i));
        }        

        detailsWindow = new DownloadDetails(this);
        detailsWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        detailsWindow.setTamanho(SearchController.humanReadableByteCount(file.getSize()));
        detailsWindow.setNome(file.getName());
        detailsWindow.setVisible(true);
        
        // Adding an incomplete file to the local file list, telling other peers that this host is a leecher of this file
        CompleteFile newFile = new CompleteFile();
        newFile.setName(file.getName());
        newFile.setSize(file.getSize());
        newFile.setComplete(false);
        newFile.setHashMD5(file.getHashMD5());
        synchronized(this.dbcontroller) {
            this.dbcontroller.addCompleteFile(newFile);            
            this.dbcontroller.incLocalVersion();
        }
    }    

    /**
     * 
     * @return The completed percentege of the download
     */
    public int getCompletedPercentage() {
        double p = this.properties.getAmoutDownloaded() * 100;
        p = p / this.file.getSize();

        return (int) p;
    }

    @Override
    public void run() {            
        long start = System.currentTimeMillis();        
        long lastSecond = 0;
        
        while (true) {
            long curTime = (System.currentTimeMillis() - start) / 1000;
            
            /* Refresh the download rate and the remaining time at least every second */             
            if(curTime - lastSecond >= 2) {
                lastSecond = curTime;
                synchronized(properties) {                    
                    properties.setDownloadRate(properties.getWindow()/2);
                    if(properties.getWindow() != 0) {
                        detailsWindow.setTempo(this.timeElapsed((this.file.getSize() - properties.getAmoutDownloaded())/properties.getWindow()));            
                    }
                    properties.setWindow(0);
                }
            }
            /* Refresh some information on the download windows */
            detailsWindow.setNConn(this.properties.getnConn() + "");
            detailsWindow.setCompleted(this.getCompletedPercentage());            
            detailsWindow.setVelocidade(properties.humanReadableByteRate());  
            detailsWindow.setTempoG(this.timeElapsed(curTime));

            /* If the download is finished */
            if (properties.getDownloadedParts() == nParts) {
                System.out.println("Iniciando escrita no arquivo...");
                break;
            }

            /* If there is space for more connections try to open new download threads */
            if (this.properties.getnConn() < IncompleteFile.MAX_CONNECTIONS && this.properties.getDownloadedParts() < this.nParts) {               
                long index; // Takes the smallest not downloaded chunk
                Long I=null;
                synchronized (parts) { 
                    if(this.parts.size() > 0) {
                        I = this.parts.remove(0);                        
                    }
                }
                if (I != null) {
                    index = I.longValue();                     
                    /* Choose somebody that probably have the desired file, this choice respects the MAX_CONNECTIONS_PER_USER constant */
                    RemoteFileList peer;
                    synchronized(this.dbcontroller) {
                        peer = dbcontroller.choosePeer(this.file.getHashMD5());                                        
                    }
                    
                    if(peer != null) {                                                
                        this.properties.setnConn(this.properties.getnConn() + 1);
                        this.detailsWindow.addPeer((int)index,peer.getIp().getHostAddress());                         
                        new Thread(new PartDownload(this, properties, detailsWindow, peer, index, this.file.getHashMD5(), dbcontroller), "Download Part: " + this.file.getName() + ", " + index).start(); // Fire a new thread responsible to download a chunk of the file                    
                    } else {
                        /* Important step: if there is no available peer return the chunk index to the queue */
                        synchronized (parts) {                            
                            this.parts.add(I);
                        }
                    }
                }
            } else {
                try {
                    synchronized (this) {
                        this.wait(); // Sleep the thread if there is nothing to do
                    }
                } catch (InterruptedException ex) {
                    System.err.println("Thread interrompida");
                }
            }
        }

        // IMPORTANTE!!! Lembrar de buscar o arquivo que acabou de ser baixado na base local e setar a flag "complete" para true
        /* Refresh the download window with the final values */                
        
        synchronized(this.dbcontroller) {
            CompleteFile newFile = this.dbcontroller.searchLocalFile(this.file.getHashMD5());
            newFile.setComplete(true);
            this.dbcontroller.incLocalVersion();
        }
        
        detailsWindow.setNConn(this.properties.getnConn() + "");
        detailsWindow.setCompleted(this.getCompletedPercentage());        
        detailsWindow.setVelocidade(properties.humanReadableByteRate());
     
        long total = (System.currentTimeMillis() - start)/1000;        
        detailsWindow.setTempoG(this.timeElapsed(total));        
        if(total != 0) {
            properties.setDownloadRate((this.file.getSize() / total));        
        }
        detailsWindow.setVelocidade(properties.humanReadableByteRate());        

        if (joinFile()) {
            System.out.println("Arquivo final escrito.");
            this.file.setComplete(true);
            synchronized(this.dbcontroller) {
                dbcontroller.incLocalVersion();
            }
        } else {
            System.err.println("Erro na hora de montar o arquivo.");
        }                
    }

    /* Join the chunk files in the final file */
    public boolean joinFile() {
        try {
            FileOutputStream finalFile = new FileOutputStream(new java.io.File(this.file.getName()));
            for (int i = 0; i < this.nParts; i++) {
                java.io.File tFile = new java.io.File(File.sharePath + "/temp/part" + i + this.file.getName());
                FileInputStream tempFile = new FileInputStream(tFile);

                if (i != this.nParts - 1) {
                    byte buffer[] = new byte[CHUNK_SIZE];
                    tempFile.read(buffer, 0, CHUNK_SIZE);
                    finalFile.write(buffer, 0, CHUNK_SIZE);
                } else {
                    byte buffer[] = new byte[(int) (this.getSize()) % (int) (IncompleteFile.CHUNK_SIZE)];
                    tempFile.read(buffer, 0, (int) (this.getSize()) % (int) (IncompleteFile.CHUNK_SIZE));
                    finalFile.write(buffer, 0, (int) (this.getSize()) % (int) (IncompleteFile.CHUNK_SIZE));
                }

                tempFile.close();
                tFile.delete();
            }

            finalFile.close();

            return true;
        } catch (IOException ex) {
            System.err.println(ex.getMessage());
            return false;
        }
    }
    
    public String timeElapsed(long time) {
        StringBuilder builder = new StringBuilder();
                
        builder.append(time/60);
        builder.append("m");
        builder.append(time%60);
        builder.append("s");
        
        return builder.toString();
    }
}
